|
Server : Apache System : Linux server.mata-lashes.com 3.10.0-1160.90.1.el7.x86_64 #1 SMP Thu May 4 15:21:22 UTC 2023 x86_64 User : matalashes ( 1004) PHP Version : 8.1.29 Disable Function : NONE Directory : /usr/src/cloud-init/tools/ |
Upload File : |
#!/usr/bin/env python3
# Provides a somewhat random, somewhat compat, somewhat useful mock version of
# http://docs.amazonwebservices.com
# /AWSEC2/2007-08-29/DeveloperGuide/AESDG-chapter-instancedata.htm
"""
To use this to mimic the EC2 metadata service entirely, run it like:
# Where 'eth0' is *some* interface.
sudo ifconfig eth0:0 169.254.169.254 netmask 255.255.255.255
sudo ./mock-meta.py -a 169.254.169.254 -p 80
Then:
wget -q http://169.254.169.254/latest/meta-data/instance-id -O -; echo
curl --silent http://169.254.169.254/latest/meta-data/instance-id ; echo
ec2metadata --instance-id
"""
import argparse
import functools
import json
import logging
import os
import random
import socket
import string
import sys
import yaml
try:
from BaseHTTPServer import HTTPServer, BaseHTTPRequestHandler
import httplib as hclient
except ImportError:
from http.server import HTTPServer, BaseHTTPRequestHandler
from http import client as hclient
log = logging.getLogger("meta-server")
EC2_VERSIONS = [
"1.0",
"2007-01-19",
"2007-03-01",
"2007-08-29",
"2007-10-10",
"2007-12-15",
"2008-02-01",
"2008-09-01",
"2009-04-04",
]
BLOCK_DEVS = [
"ami",
"ephemeral0",
"root",
]
DEV_PREFIX = "v" # This seems to vary alot depending on images...
DEV_MAPPINGS = {
"ephemeral0": "%sda2" % (DEV_PREFIX),
"root": "/dev/%sda1" % (DEV_PREFIX),
"ami": "%sda1" % (DEV_PREFIX),
"swap": "%sda3" % (DEV_PREFIX),
}
META_CAPABILITIES = [
"aki-id",
"ami-id",
"ami-launch-index",
"ami-manifest-path",
"ari-id",
"block-device-mapping/",
"hostname",
"instance-action",
"instance-id",
"instance-type",
"local-hostname",
"local-ipv4",
"placement/",
"product-codes",
"public-hostname",
"public-ipv4",
"public-keys/",
"reservation-id",
"security-groups",
]
PUB_KEYS = {
"brickies": [
"ssh-rsa "
"AAAAB3NzaC1yc2EAAAABIwAAAQEA3I7VUf2l5gSn5uavROsc5HRDpZdQueUq5ozemN"
"Sj8T7enqKHOEaFoU2VoPgGEWC9RyzSQVeyD6s7APMcE82EtmW4skVEgEGSbDc1pvxz"
"xtchBj78hJP6Cf5TCMFSXw+Fz5rF1dR23QDbN1mkHs7adr8GW4kSWqU7Q7NDwfIrJJ"
"tO7Hi42GyXtvEONHbiRPOe8stqUly7MvUoN+5kfjBM8Qqpfl2+FNhTYWpMfYdPUnE7"
"u536WqzFmsaqJctz3gBxH9Ex7dFtrxR4qiqEr9Qtlu3xGn7Bw07/+i1D+ey3ONkZLN"
"+LQ714cgj8fRS4Hj29SCmXp5Kt5/82cD/VN3NtHw== brickies",
"",
],
}
INSTANCE_TYPES = [
"m1.large",
"m1.medium",
"m1.small",
"m1.xlarge",
]
AVAILABILITY_ZONES = [
"us-east-1a",
"us-east-1b",
"us-east-1c",
"us-east-1d",
"eu-west-1a",
"eu-west-1b",
"us-west-1",
]
PLACEMENT_CAPABILITIES = {
"availability-zone": AVAILABILITY_ZONES,
}
NOT_IMPL_RESPONSE = json.dumps({})
class WebException(Exception):
def __init__(self, code, msg):
Exception.__init__(self, msg)
self.code = code
def yamlify(data):
formatted = yaml.dump(
data,
line_break="\n",
indent=4,
explicit_start=True,
explicit_end=True,
default_flow_style=False,
)
return formatted
def format_text(text):
if not len(text):
return "<<"
lines = text.splitlines()
nlines = []
for line in lines:
nlines.append("<< %s" % line)
return "\n".join(nlines)
def traverse(keys, mp):
result = dict(mp)
for k in keys:
try:
result = result.get(k)
except (AttributeError, TypeError):
result = None
break
return result
ID_CHARS = [c for c in (string.ascii_uppercase + string.digits)]
def id_generator(size=6, lower=False):
txt = "".join(random.choice(ID_CHARS) for x in range(size))
if lower:
return txt.lower()
else:
return txt
def get_ssh_keys():
keys = {}
keys.update(PUB_KEYS)
# Nice helper to add in the 'running' users key (if they have one)
key_pth = os.path.expanduser("~/.ssh/id_rsa.pub")
if not os.path.isfile(key_pth):
key_pth = os.path.expanduser("~/.ssh/id_dsa.pub")
if os.path.isfile(key_pth):
with open(key_pth, "rb") as fh:
contents = fh.read()
keys[os.getlogin()] = [contents, ""]
return keys
class HTTPServerV6(HTTPServer):
address_family = socket.AF_INET6
class MetaDataHandler:
def __init__(self, opts):
self.opts = opts
self.instances = {}
def get_data(self, params, who, **kwargs):
if not params:
# Show the root level capabilities when
# no params are passed...
caps = sorted(META_CAPABILITIES)
return "\n".join(caps)
action = params[0]
action = action.lower()
if action == "instance-id":
return "i-%s" % (id_generator(lower=True))
elif action == "ami-launch-index":
return "%s" % random.choice([0, 1, 2, 3])
elif action == "aki-id":
return "aki-%s" % (id_generator(lower=True))
elif action == "ami-id":
return "ami-%s" % (id_generator(lower=True))
elif action == "ari-id":
return "ari-%s" % (id_generator(lower=True))
elif action == "block-device-mapping":
nparams = params[1:]
if not nparams:
return "\n".join(BLOCK_DEVS)
else:
subvalue = traverse(nparams, DEV_MAPPINGS)
if not subvalue:
return "\n".join(sorted(list(DEV_MAPPINGS.keys())))
else:
return str(subvalue)
elif action in ["hostname", "local-hostname", "public-hostname"]:
# Just echo back there own hostname that they called in on..
return "%s" % (who)
elif action == "instance-type":
return random.choice(INSTANCE_TYPES)
elif action == "ami-manifest-path":
return "my-amis/spamd-image.manifest.xml"
elif action == "security-groups":
return "default"
elif action in ["local-ipv4", "public-ipv4"]:
# Just echo back there own ip that they called in on...
return "%s" % (kwargs.get("client_ip", "10.0.0.1"))
elif action == "reservation-id":
return "r-%s" % (id_generator(lower=True))
elif action == "product-codes":
return "%s" % (id_generator(size=8))
elif action == "public-keys":
nparams = params[1:]
# This is a weird kludge, why amazon why!!!
# public-keys is messed up, list of /latest/meta-data/public-keys/
# shows something like: '0=brickies'
# but a GET to /latest/meta-data/public-keys/0=brickies will fail
# you have to know to get '/latest/meta-data/public-keys/0', then
# from there you get a 'openssh-key', which you can get.
# this hunk of code just re-works the object for that.
avail_keys = get_ssh_keys()
key_ids = sorted(list(avail_keys.keys()))
if nparams:
mybe_key = nparams[0]
try:
key_id = int(mybe_key)
key_name = key_ids[key_id]
except ValueError as e:
raise WebException(
hclient.BAD_REQUEST, "%s: not an integer" % mybe_key
) from e
except IndexError as e:
raise WebException(
hclient.NOT_FOUND, "Unknown key id %r" % mybe_key
) from e
# Extract the possible sub-params
result = traverse(
nparams[1:],
{
"openssh-key": "\n".join(avail_keys[key_name]),
},
)
if isinstance(result, (dict)):
# TODO(harlowja): This might not be right??
result = "\n".join(sorted(result.keys()))
if not result:
result = ""
return result
else:
contents = []
for (i, key_id) in enumerate(key_ids):
contents.append("%s=%s" % (i, key_id))
return "\n".join(contents)
elif action == "placement":
nparams = params[1:]
if not nparams:
pcaps = sorted(PLACEMENT_CAPABILITIES.keys())
return "\n".join(pcaps)
else:
pentry = nparams[0].strip().lower()
if pentry == "availability-zone":
zones = PLACEMENT_CAPABILITIES[pentry]
return "%s" % random.choice(zones)
else:
return "%s" % (PLACEMENT_CAPABILITIES.get(pentry, ""))
else:
log.warning(
"Did not implement action %s, returning empty response: %r",
action,
NOT_IMPL_RESPONSE,
)
return NOT_IMPL_RESPONSE
class UserDataHandler:
def __init__(self, opts):
self.opts = opts
def _get_user_blob(self, **kwargs):
blob = None
if self.opts["user_data_file"] is not None:
blob = self.opts["user_data_file"]
if not blob:
blob_mp = {
"hostname": kwargs.get("who", "localhost"),
}
lines = [
"#cloud-config",
yamlify(blob_mp),
]
blob = "\n".join(lines)
return blob.strip()
def get_data(self, params, who, **kwargs):
if not params:
return self._get_user_blob(who=who)
return NOT_IMPL_RESPONSE
# Seem to need to use globals since can't pass
# data into the request handlers instances...
# Puke!
meta_fetcher = None
user_fetcher = None
class Ec2Handler(BaseHTTPRequestHandler):
def _get_versions(self):
versions = ["latest"] + EC2_VERSIONS
versions = sorted(versions)
return "\n".join(versions)
def log_message(self, fmt, *args):
msg = "%s - %s" % (self.address_string(), fmt % (args))
log.info(msg)
def _find_method(self, path):
# Puke! (globals)
func_mapping = {
"user-data": user_fetcher.get_data,
"meta-data": meta_fetcher.get_data,
}
segments = [piece for piece in path.split("/") if len(piece)]
log.info("Received segments %s", segments)
if not segments:
return self._get_versions
date = segments[0].strip().lower()
if date not in self._get_versions():
raise WebException(
hclient.BAD_REQUEST, "Unknown version format %r" % date
)
if len(segments) < 2:
raise WebException(hclient.BAD_REQUEST, "No action provided")
look_name = segments[1].lower()
if look_name not in func_mapping:
raise WebException(
hclient.BAD_REQUEST, "Unknown requested data %r" % look_name
)
base_func = func_mapping[look_name]
who = self.address_string()
ip_from = self.client_address[0]
if who == ip_from:
# Nothing resolved, so just use 'localhost'
who = "localhost"
kwargs = {
"params": list(segments[2:]),
"who": who,
"client_ip": ip_from,
}
return functools.partial(base_func, **kwargs)
def _do_response(self):
who = self.client_address
log.info("Got a call from %s for path %s", who, self.path)
try:
func = self._find_method(self.path)
data = func()
if not data:
data = ""
self.send_response(hclient.OK)
self.send_header("Content-Type", "binary/octet-stream")
self.send_header("Content-Length", len(data))
log.info(
"Sending data (len=%s):\n%s", len(data), format_text(data)
)
self.end_headers()
self.wfile.write(data.encode())
except RuntimeError as e:
log.exception("Error somewhere in the server.")
self.send_error(hclient.INTERNAL_SERVER_ERROR, message=str(e))
except WebException as e:
code = e.code
log.exception(str(e))
self.send_error(code, message=str(e))
def do_GET(self):
self._do_response()
def do_POST(self):
self._do_response()
def setup_logging(log_level, fmt="%(levelname)s: @%(name)s : %(message)s"):
root_logger = logging.getLogger()
console_logger = logging.StreamHandler(sys.stdout)
console_logger.setFormatter(logging.Formatter(fmt))
root_logger.addHandler(console_logger)
root_logger.setLevel(log_level)
def extract_opts():
parser = argparse.ArgumentParser()
parser.add_argument(
"-p",
"--port",
dest="port",
action="store",
type=int,
default=80,
metavar="PORT",
help="port from which to serve traffic (default: %default)",
)
parser.add_argument(
"-a",
"--addr",
dest="address",
action="store",
type=str,
default="::",
metavar="ADDRESS",
help="address from which to serve traffic (default: %default)",
)
parser.add_argument(
"-f",
"--user-data-file",
dest="user_data_file",
action="store",
metavar="FILE",
help="user data filename to serve back toincoming requests",
)
parser.add_argument("extra", nargs="*")
args = parser.parse_args()
out = {
"port": args.port,
"address": args.address,
"extra": args.extra,
"user_data_file": None,
}
if args.user_data_file:
if not os.path.isfile(args.user_data_file):
parser.error("Option -f specified a non-existent file")
with open(args.user_data_file, "rb") as fh:
out["user_data_file"] = fh.read()
return out
def setup_fetchers(opts):
global meta_fetcher
global user_fetcher
meta_fetcher = MetaDataHandler(opts)
user_fetcher = UserDataHandler(opts)
def run_server():
# Using global here since it doesn't seem like we
# can pass opts into a request handler constructor...
opts = extract_opts()
setup_logging(logging.DEBUG)
setup_fetchers(opts)
log.info("CLI opts: %s", opts)
server_address = (opts["address"], opts["port"])
server = HTTPServerV6(server_address, Ec2Handler)
sa = server.socket.getsockname()
log.info("Serving ec2 metadata on %s using port %s ...", sa[0], sa[1])
server.serve_forever()
if __name__ == "__main__":
run_server()
# vi: ts=4 expandtab